/**
  ******************************************************************************
  * @file    input_output.c
  * @author  ashuai0110
  * @version V2.3
  * @date    2024-07-01
  * @brief   IO输入输出操作
  *          (输入可以设置各边沿触发时间,输出可以设置各边沿保持时间)
  *
  ******************************************************************************
  * @attention
  *
  * 版权声明:内容为编者(ashuai0110)原创,使用请注明出处,当然,你也可以不这样做^_^
  * 出处链接:https://gitee.com/ashuai0110/mcu_reuse_development_module.git
  *
  * Change Logs:
  * Date          Author        Notes
  * 2023-09-19    ashuai0110    完成基本内容
  * 2024-01-01    ashuai0110    修改链表挂起逻辑
  *
  ******************************************************************************
  */

/* 包含头文件-----------------------------------------------------------------*/
#include "input_output.h"

/**
 * @addtogroup modules
 * @{
 */

/**
 * @defgroup input_output input_output
 * @{
 */

/* 私有宏定义-----------------------------------------------------------------*/
/**
 * @defgroup input_output_local_macros input output local macros
 * @{
 */

/**
 * @defgroup input_output_check_parameters_validity input output check parameters validity
 * @{
 */
/* IO操作IO方向有效性检查 */
#define IS_VALID_DIR(x)                                                       \
(       ((x) == IO_DIR_OUT)                 ||                                \
        ((x) == IO_DIR_IN))

/* IO操作运行标志有效性检查 */
#define IS_VALID_RUN_FLAG(x)                                                  \
(       ((x) == IO_RUN_STOP)                ||                                \
        ((x) == IO_RUN_START))
/**
 * @}
 */

/**
 * @}
 */

/* 私有类型定义---------------------------------------------------------------*/

/* 私有变量-------------------------------------------------------------------*/
/**
 * @defgroup input_output_local_variables input output local variables
 * @{
 */

static io_t *pIOTaskHead = NULL; /*!< IO操作工作链表头节点 */

/**
 * @}
 */

/* 全局变量-------------------------------------------------------------------*/

/* 私有函数原型---------------------------------------------------------------*/
/**
 * @defgroup input_output_local_functions input output local functions
 * @{
 */

/**
  * @brief  IO输入处理
  *
  * @param  _io             : IO操作实例
  *
  * @retval uint8_t         : 输入边沿类型 @ref in_edge_type
  *    @arg IN_EDGE_NONE    : 无边沿触发
  *    @arg IN_EDGE_RISING  : 上升沿触发
  *    @arg IN_EDGE_FALLING : 下降沿触发
  */
static uint8_t io_input_handle(io_t *_io)
{
    uint8_t readLevel;
    
    ASSERT_PARAM(IS_VALID_POINT(_io));
    
    if(_io->state > 0) { _io->ticks++; } /* 大于0说明边沿已变化需计时 */
    readLevel = _io->rwFn(0); /* 读取当前io电平 */

    switch(_io->state)
    {
        case 0: /* 首次变化 */
            _io->ticks = 0; /* 未出现变化则置零计时计数值 */
            /* 当前io电平不等于默认电平说明出现变化  */
            if(readLevel != _io->level)
            {
                /* 当前io是1则出现上升沿  当前io是0则出现下降沿 */
                _io->edge = (readLevel == 1) ? IN_EDGE_RISING : IN_EDGE_FALLING;
                _io->level = readLevel; /* 记录当前io电平 */
                _io->state = 1; /* 状态切换 */
                goto checkTicks;
            }
            break;
        case 1: /* 边沿保持或二次变化 */
            /* 若电平不变化则goto */
            if(readLevel == _io->level)
            {
                goto checkTicks;
            } /* 若电平变化说明保持时间不足需更改为变化后的边沿类型继续 */
            else
            {
                _io->level = readLevel; /* 记录当前io电平 */
                _io->edge = (_io->edge == IN_EDGE_RISING) ? IN_EDGE_FALLING : IN_EDGE_RISING;  /* 修改触发边沿 */
                goto checkTicks;
            }
        default: break;
    }

checkTicks:
    /* 如果触发边沿是上升沿且达到上升沿触发时间则返回上升沿触发 */
    if(IN_EDGE_RISING == _io->edge && _io->ticks >= _io->risingTicks)
    {
        _io->state = 0; /* 状态复位 */
        _io->edge = IN_EDGE_NONE; /* 触发边沿复位 */
        return IN_EDGE_RISING;
    } /* 如果触发边沿是下降沿且达到下降沿触发时间则返回下降沿触发 */
    else if(IN_EDGE_FALLING == _io->edge && _io->ticks >= _io->fallingTicks)
    {
        _io->state = 0; /* 状态复位 */
        _io->edge = IN_EDGE_NONE; /* 触发边沿复位 */
        return IN_EDGE_FALLING;
    }

    return IN_EDGE_NONE;
}

/**
  * @brief  IO输出处理
  *
  * @note   None
  * @param  _io           : IO操作实例
  *
  * @retval uint8_t       : 输出一个周期状态 @ref out_cycle_state
  *    @arg OUT_CYCLE_END : 输出一个周期结束
  *    @arg OUT_CYCLE_ING : 输出一个周期进行中
  */
static uint8_t io_output_handle(io_t *_io)
{
    ASSERT_PARAM(IS_VALID_POINT(_io));
    
    if(_io->state > 0) { _io->ticks++; } /* 大于0说明边沿已变化需计时 */
    
    switch(_io->state)
    {
        case 0: /* 输出开始,默认开始先输出高电平 */
            /* 若上升沿时间不为0则输出高电平 */
            if(_io->risingTicks)
            {
                _io->state = 1; /* 切换状态 */
                _io->rwFn(1);
            }
            else
            {   /* 否则查看下降沿时间不为0则输出低电平 */
                if(_io->fallingTicks)
                {
                    _io->state = 2; /* 切换状态 */
                    _io->rwFn(0);
                }
            }
            break;
        case 1: /* 输出高电平保持 */
            /* 若计时超过上升沿时间 */
            if(_io->ticks >= _io->risingTicks)
            {
                _io->ticks = 0; /* 清零计时计数值 */
                /* 查看下降沿时间不为0则输出低电平 */
                if(_io->fallingTicks)
                {
                    _io->state = 2; /* 切换状态 */
                    _io->rwFn(0);
                } /* 否则认为输出一个周期结束 */
                else
                {
                    return OUT_CYCLE_END;
                }
            }
            break;
        case 2: /* 输出低电平保持 */
            /* 若计时超过下降沿时间则认为输出一个周期结束 */
            if(_io->ticks >= _io->fallingTicks)
            {
                _io->ticks = 0; /* 清零计时计数值 */
                _io->state = 0; /* 状态复位 */
                
                return OUT_CYCLE_END;
            }
            break;
        default: break;
    }

    return OUT_CYCLE_ING;
}

/**
 * @}
 */

/**
 * @defgroup input_output_global_functions input output global functions
 * @{
 */

/**
  * @brief  IO操作任务初始化
  *
  * @note   注意IO方向和读写函数对应,初始化后便不能修改
  *
  * @param  _io          : IO操作实例
  *
  * @param  rwFn         : IO方向是输入时应为IO读函数 / IO方向是输出时应为IO写函数
  *
  * @param  callbackFn   : IO操作回调函数
  *
  * @param  dir          : IO方向 @ref io_dir
  *   @arg  IO_DIR_OUT   : 输出方向
  *   @arg  IO_DIR_IN    : 输入方向
  *
  * @param  run          : 运行标志 @ref io_run_flg
  *   @arg  IO_RUN_STOP  : 不运行
  *   @arg  IO_RUN_START : 运行
  *
  * @retval uint8_t
  *    @arg RET_OK       : 成功
  *    @arg RET_ERR      : 失败
  */
uint8_t io_init(io_t *_io, io_rw_fn_t rwFn, io_cb_fn_t callbackFn, uint8_t dir, uint8_t run)
{
    ASSERT_PARAM(IS_VALID_POINT(_io));
    ASSERT_PARAM(IS_VALID_POINT(rwFn));
    ASSERT_PARAM(IS_VALID_DIR(dir));
    ASSERT_PARAM(IS_VALID_RUN_FLAG(run));

    /* 初始化各参数 */
    memset(_io, 0, sizeof(io_t));
    _io->rwFn = rwFn;
    _io->dir = dir;
    _io->run = run;
    _io->level = (_io->dir == IO_DIR_IN) ? (_io->rwFn(0)) : 0; /* 默认电平由此时读到的电平决定 */
    _io->edge = IN_EDGE_NONE;
    _io->callbackFn = callbackFn;
    /* 初始化时就插入链表 */
    return io_insert(_io);
}

/**
  * @brief  IO操作任务取消初始化
  *
  * @param  _io : IO操作实例
  *
  * @retval None
  */
void io_de_init(io_t *_io)
{
    ASSERT_PARAM(IS_VALID_POINT(_io));
    
    /* 从链表取下并置零所有参数 */
    io_pending(_io);
    memset(_io, 0, sizeof(io_t));
}

/**
  * @brief  IO操作任务插入工作链表
  *
  * @param  _io     : IO操作实例
  *
  * @retval uint8_t
  *    @arg RET_OK  : 成功
  *    @arg RET_ERR : 已存在(已被插入)
  */
uint8_t io_insert(io_t *_io)
{
    io_t *ioTarget;
    
    ASSERT_PARAM(IS_VALID_POINT(_io));
    
    ioTarget = pIOTaskHead; /* 拿到链表头节点 */
    /* 遍历查重 重复则返回错误 */
    while(ioTarget)
    {
        if(ioTarget == _io) { return RET_ERR; }
        ioTarget = ioTarget->pNext;
    }
    /* 否则使用头插法(后来居上)将此节点插入链表 */
    _io->pNext = pIOTaskHead;
    pIOTaskHead = _io;

    return RET_OK;
}

/**
  * @brief  IO操作任务挂起(从工作链表中取下)
  *
  * @param  _io     : IO操作实例
  *
  * @retval uint8_t
  *    @arg RET_OK  : 成功
  *    @arg RET_ERR : 不存在(已被挂起)
  */
uint8_t io_pending(io_t *_io)
{
    io_t **ioTarget;
    
    ASSERT_PARAM(IS_VALID_POINT(_io));

    ioTarget = &pIOTaskHead; /* 先拿到头节点的地址 */
    /* 遍历寻找此节点 */
    while(*ioTarget != _io)
    {
        if(NULL == *ioTarget) { return RET_ERR; }
        ioTarget = &(*ioTarget)->pNext;
    }
    *ioTarget = _io->pNext; /* 将此节点指针改为下一节点指针 即移除此节点 */

    return RET_OK;
}

/**
  * @brief  更新IO上升沿和下降沿计数值
  *
  * @param  _io          : IO操作实例
  *
  * @param  risingTicks  : 上升沿输入触发计数值(输入方向) / 上升沿输出保持计数值(输出方向)
  *                        例如: 上升沿输入500ms后触发,调度器周期10ms,risingTicks=500ms/10ms=50
  *
  * @param  fallingTicks : 下降沿输入触发计数值(输入方向) / 下降沿输出保持计数值(输出方向)
  *                        例如: 下降沿输出保持1000ms,调度器周期10ms,risingTicks=1000ms/10ms=100
  *
  * @retval None
  */
void io_update_ticks(io_t *_io, uint32_t risingTicks, uint32_t fallingTicks)
{
    ASSERT_PARAM(IS_VALID_POINT(_io));

    /* 更新参数 */
    _io->risingTicks = risingTicks;
    _io->fallingTicks = fallingTicks;
}

/**
  * @brief  IO操作轮询处理
  *
  * @note   此函数执行周期即是IO操作任务计数值累加周期,一般放入定时器中
  *
  * @param  None
  *
  * @retval None
  */
void io_poll(void)
{
    static io_t *ioTarget;
    static uint8_t result;

    /* 遍历链表 */
    for(ioTarget = pIOTaskHead; ioTarget; ioTarget = ioTarget->pNext)
    {
        /* 运行标志为开启运行才执行 */
        if(IO_RUN_START == ioTarget->run)
        {
            /* 根据io方向执行不同处理 */
            if(IO_DIR_IN == ioTarget->dir)
            {
                result = io_input_handle(ioTarget);
                /* 返回有效的输入触发边沿则执行用户回调函数 */
                if(IN_EDGE_NONE != result)
                {
                    ioTarget->callbackFn(result);
                }
            }
            else
            {
                result = io_output_handle(ioTarget);
                /* 返回一个周期输出完成则执行用户回调函数 */
                if(OUT_CYCLE_END == result)
                {
                    ioTarget->callbackFn(0xFF);
                }
            }
        }
    }
}

/**
  * @brief  设置IO上升沿计数值
  *
  * @param  _io         : IO操作实例
  *
  * @param  risingTicks : 上升沿计数值
  *
  * @retval None
  */
void io_set_rising_ticks(io_t *_io, uint32_t risingTicks)
{
    ASSERT_PARAM(IS_VALID_POINT(_io));
    
    _io->risingTicks = risingTicks;
}

/**
  * @brief  获取IO上升沿计数值
  *
  * @param  _io : IO操作实例
  *
  * @retval IO上升沿计数值
  */
uint32_t io_get_rising_ticks(io_t *_io)
{
    ASSERT_PARAM(IS_VALID_POINT(_io));
    
    return _io->risingTicks;
}

/**
  * @brief  设置IO下降沿计数值
  *
  * @param  _io          : IO操作实例
  *
  * @param  fallingTicks : 下降沿计数值
  *
  * @retval None
  */
void io_set_falling_ticks(io_t *_io, uint32_t fallingTicks)
{
    ASSERT_PARAM(IS_VALID_POINT(_io));
    
    _io->fallingTicks = fallingTicks;
}

/**
  * @brief  获取IO下降沿计数值
  *
  * @param  _io : IO操作实例
  *
  * @retval IO下降沿计数值
  */
uint32_t io_get_falling_ticks(io_t *_io)
{
    ASSERT_PARAM(IS_VALID_POINT(_io));
    
    return _io->fallingTicks;
}

/**
  * @brief  设置IO任务运行标志
  *
  * @param  _io          : IO操作实例
  *
  * @param  runFlg       : 运行标志 @ref io_run_flg
  *   @arg  IO_RUN_STOP  : 不运行
  *   @arg  IO_RUN_START : 运行
  *
  * @retval None
  */
void io_set_run(io_t *_io, uint8_t runFlg)
{
    ASSERT_PARAM(IS_VALID_POINT(_io));
    
    _io->run = runFlg;
}

/**
  * @brief  获取IO任务运行标志
  *
  * @param  _io          : IO操作实例
  *
  * @retval uint8_t      : 运行标志 @ref io_run_flg
  *    @arg IO_RUN_STOP  : 不运行
  *    @arg IO_RUN_START : 运行
  */
uint8_t io_get_run(io_t *_io)
{
    ASSERT_PARAM(IS_VALID_POINT(_io));
    
    return _io->run;
}

/**
 * @}
 */

/**
 * @}
 */

/**
 * @}
 */
